建立 cloud functions 抓取電影時刻表,讓 cloud scheduler 可以定期觸發,並將今日放映的電影資訊顯示在 Slack 上。
將先前寫的抓取電影時刻的程式碼使用 @google-cloud/functions-framework 包成 cloud functions 形式,選擇 HTTP Trigger,並勾選 「需要驗證」。
index.js 內容如下:
'use strict';
const functions = require("@google-cloud/functions-framework");
const axios = require("axios");
const cheerio = require("cheerio");
const url = "https://meet.eslite.com/tw/tc/gallery/movieschedule/201803020001"; // 目標網址
// getTimetable 為 entry point
functions.http("getTimetable", async(req, res) => {
  let externalRes = await axios.get(url);
  let $ = cheerio.load(externalRes.data);
  let list = [];
  $(".film_list .box").each(function(i, elem) {
    let name = $(this).find(".intro .left > p").text();
    let timetable = [];
    $(this).find(".time-swiper .swiper-slide").each(function (j, slide) {
      let date = $(slide).find("p").text();
      let time = [] ;
      $(slide).find("ul li").each(function(k, text) {
        time.push($(text).text());
      })
      timetable.push({date, time});
    })
    list.push({name, timetable});
  });
  res.status(200).send(list);
})
Cloud Scheduler 可以用來呼叫需驗證的 HTTP targets,例如:Cloud Functions or Cloud Run。Function URL 即為要執行的目標(HTTP targets)。
注意事項:
Cloud Scheduler 在同一個專案中Cloud Scheduler 預設的服務帳戶Google Cloud 中的目標對象,可透過 IAM (Identity and Access Management) 管理服務帳戶
*補充:服務帳戶 Service account?
利用服務帳戶來呼叫需驗證的 API,服務帳戶可作為 IAM 主體/使用者,管理服務帳戶對Google Cloud資源的存取權限。
服務帳戶類似於在Google Cloud建立一個虛擬使用者。
進入IAM 與管理 - 服務帳戶,點擊建立服務帳戶
進入 「Cloud Scheduler」 點擊建立工作
0 8 * * * 、選擇台北時區
*gcloud 操作可參考 Google Cloud 官方文件 - Use authentication with HTTP targets
假設需要將 Functions 執行的結果送到 Slack 通知,可依 Sending messages using Incoming Webhooks 說明操作。
透過 Slack 的 Block Kit Builder 客製訊息的樣式,或是直接選擇現成的 Template 進行調整
使用 try...catch 處理錯誤,index.js 建立 notifySlack 函式傳送 slack 訊息。
// index.js
// 略
const notifySlack = async (payload) => {
  await axios.post(slackUrl, payload)
}
functions.http('getTimetable', async(req, res) => {
  try {
    // 略
    notifySlack('成功取得電影時刻');
  } catch (e) {
    notifySlack('無法取得電影時刻');
  }
})
使用 Slack 的 Block Kit Builder 將欲顯示的電影資訊做樣式排版,製作 Slack 訊息的樣板,如下圖:
完整 Cloud Functions 內容
'use strict';
const functions = require('@google-cloud/functions-framework');
const axios = require("axios");
const cheerio = require("cheerio");
const url = "https://meet.eslite.com/tw/tc/gallery/movieschedule/201803020001";
const slackUrl = "https://hooks.slack.com/services/xxxxxxxxxxxxxx";
const movieResultTemplate = require("./slack-movie-card");
const notifySlack = async (payload) => {
  await axios.post(slackUrl, payload)
}
functions.http('getTimetable', async(req, res) => {
  try {
    let externalRes = await axios.get(url);
    let $ = cheerio.load(externalRes.data);
    let list = [];
    $(".film_list .box").each(function(i, elem) {
      // 電影名稱
      let name = $(this).find(".intro .left > p").text();
      // 電影介紹連結
      let link = `https://meet.eslite.com${$(this).find(".intro .right .btn-detail").attr("href")}`;
      // 電影縮圖連結
      let thumbUrl = `https://meet.eslite.com${$(this).find(".img img").attr("src")}`;
      // 電影縮圖文字
      let thumbAlt = $(this).find(".img img").attr("alt");
      // 電影資訊
      let infos = [];
      // 時刻表
      let timetable = [];
      // 取得電影資訊 (級別、片長...)
      $(this).find(".intro .right > ul > li").each(function (index, li) {
        let info = $(li).text();
        infos.push(info);
      });
      // 取得電影每日時刻
      $(this).find(".time-swiper .swiper-slide").each(function (j, slide) {
        let date = $(slide).find("p").text();
        let time = [];
        $(slide).find("ul li").each(function(k, text) {
          time.push($(text).text());
        })
        timetable.push({date, time});
      })
      list.push({name, link, thumbUrl, thumbAlt, infos, timetable})
    });
    // ---- 轉換成 slack template 格式 ----
    let movieResult = {...movieResultTemplate.result},
        cardResult = [],
        today = `${new Date().getMonth() + 1}/${new Date().getDate()}`;
    // 遍歷每一部電影資訊
    list.forEach((item) => {
      const regex = /\d+\/\d+/;
      // 判斷第一個放映日期是否為今日
      let movieDate = item.timetable[0].date.match(regex)[0];
      if (movieDate !== today) return;
      let time = item.timetable[0].time.join(' | ');
      let card = JSON.parse(JSON.stringify(movieResultTemplate.card));
      let infos = item.infos.join('\n');
      card[0].text.text = `*<${item.link}|${item.name}>*\n${infos}\n時刻: ${time}`;
      card[0].accessory.image_url = item.thumbUrl;
      card[0].accessory.alt_text = item.thumbAlt;
      cardResult.push(...card);
    })
    movieResult.blocks[0].text.text = `${today} 今日放映 :movie_camera:`
    movieResult.blocks.push(...cardResult);
    notifySlack((movieResult));
    res.status(200).send(list);
  } catch (e) {
    console.log(e);
    notifySlack({text:`:bangbang: 無法取得電影時刻`, emoji: true});
  }
})
每日 8:00 排程會執行 Cloud Functions,到電影時刻表網頁抓取資訊,將相關資訊整理成 Slack 訊息格式,透過 Slack Webhook 發送訊息到 Channel 中。
*Slack 訊息
*排程執行狀態
Use authentication with HTTP targets
Using-scheduler-invoke-private-functions-OIDC
Sending messages using Incoming Webhooks